/**
 * @ Author: luoqi
 * @ Create Time: 2024-04-17 18:01
 * @ Modified by: luoqi
 * @ Modified time: 2024-10-12 16:15
 * @ Description:
 */

#include <bsp.h>
#include "qboot.h"
#include "ymodem.h"
#include "../qshell/qterm.h"

#if QBOOT_DISP_DBG_INFO
#define QBOOT_DBG(...)  QBOOT_DBG(__VA_ARGS__)
#else 
#define QBOOT_DBG(...) 
#endif

static QCliCmd _cmd_qboot;
static int _cmd_qboot_hdl(int argc, char **argv);

static QBoot _boot;
static YModem _ymodem;

static uint8_t buf[1024 + 6];

static int _putc(uint8_t c)
{
    return _boot.send(&c, 1);
}

static int _getc(uint8_t *c, int timeout_ms)
{
    return _boot.recv(c, 1, timeout_ms);
}

typedef void (*Jump)(void);
int qboot_jump(uint32_t addr)
{
    Jump jmp;
    uint32_t appstack = *(volatile uint32_t *)QBOOT_APP_ADDR;
    if((appstack & 0x2ffe0000) != 0x20000000) {
        qterm_call("blink stat norm");
        QSH(" #! Stack pointer is not valid, appstack: 0x%08X !\r\n", appstack);
        return -1;
    } else {
        QSH("\r\n Jump to app, addr: 0x%08X, appstack: 0x%08X ...\r\n", addr, appstack);
        volatile uint32_t jmp_addr = *(volatile uint32_t *)(addr + 4);
        jmp = (Jump)jmp_addr;
        __set_PSP(*(volatile uint32_t *)QBOOT_APP_ADDR);
        __set_CONTROL(0);
        __set_MSP(*(volatile uint32_t *)QBOOT_APP_ADDR);
        jmp();
    }
    return 0;
}

static int _ymodem_start_cb(const uint8_t *buf, int len)
{
    qterm_call("blink stat updating");
    QBOOT_DBG("\r\n Firmware Download start...\r\n");
    int offset = 0;
    for(int i = 0; i < len; i++) {
        if((buf[i] != 0) && (buf[i] != 0x1a)) {
            offset++;
        } else {
            break;
        }
    }
    int sz = 0;
    for(int i = offset + 1; i < len - offset; i++) {
        if(buf[i] >= 48 && buf[i] <= 57) {
            sz = sz * 10 + (buf[i] - '0');
        } else {
            break;
        }
    }
    QBOOT_DBG(" FileName: %s, FileSize: %d\r\n", buf, sz);
    QBOOT_DBG(" Erase flash, addr: %08X, size: %d\r\n", QBOOT_APP_ADDR, sz - 2000);
    int err = _boot.flash_erase(QBOOT_APP_ADDR, sz - 2000);
    if(err < 0) {
        QBOOT_DBG(" Erase flash failed, addr: %d, size: %d, ecode: %d\r\n", QBOOT_APP_ADDR, sz - 2000, err);
    }
    return 0;
}

static int _ymodem_data_cb(const uint8_t *buf, int len)
{
    static uint32_t offset = 0;
    int err = 0;
    err = _boot.flash_write(QBOOT_APP_ADDR + offset, buf, len);
    if(err < 0) {
        QBOOT_DBG(" Flash write failed, addr: %d, sz: %d, ecode: %d\r\n", QBOOT_APP_ADDR + offset, len, err);
    }
    offset += len;
    return err;
}

static int _ymodem_end_cb(const uint8_t *buf, int len)
{
    qterm_call("blink stat finish");
    bsp_mdelay(10);
    QBOOT_DBG(" Firmware download finished\r\n");
    return 0;
}

int qboot_init(int (*send)(const uint8_t *, uint32_t), int (*recv)(uint8_t *, uint32_t, uint32_t), int (*flash_write)(uint32_t, const uint8_t *, int), int (*flash_erase)(uint32_t, int))
{
    _boot.send = send;
    _boot.recv = recv;
    _boot.flash_write = flash_write;
    _boot.flash_erase = flash_erase;
    ymodem_init(&_ymodem, buf, _putc, _getc, _ymodem_start_cb, _ymodem_data_cb, _ymodem_end_cb);

    qterm_attach(&_cmd_qboot, "qboot", _cmd_qboot_hdl, "qboot");
    QBOOT_DBG("\r QBOOT Version: v%d.%d.%d, build: %s\r\n", QBOOT_VERSION_MAJOR, QBOOT_VERSION_MINOR, QBOOT_VERSION_PATCH, __DATE__);
    return 0;
}

int qboot_exec()
{
    int err = ymodem_download(&_ymodem, 30);
    if(err < 0) {
        QBOOT_DBG("\r\n #! ymodem download failed, ecode: %d\r\n", err);
        qterm_call("blink stat norm");
        return -1;
    } else {
        qboot_jump(QBOOT_APP_ADDR);
        return -1;
    }
    return 0;
}

int _cmd_qboot_hdl(int argc, char **argv)
{
    if(argc == 1) {
        QSH(" -jmp\r\n");
    } else if(ISARG(argv[1], "jmp") && argc == 2) {
        qboot_jump(QBOOT_APP_ADDR);
    }
    return 0;
}
